{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook demonstrates the procedure presented in\n",
    "\n",
    "P. Bestagini, F. Lombardi, M. Lualdi, F. Picetti, S. Tubaro, <i>Landmine Detection Using Autoencoders on Multi-polarization GPR Volumetric Data</i>, Oct. 2018"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "from sklearn.utils import shuffle\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "import PreProcessing\n",
    "import net\n",
    "from python_patch_extractor import PatchExtractor\n",
    "\n",
    "from keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "in_path = 'giuriati_2/20170621_deg0_HHVV.npy'\n",
    "out_path = 'cnn_article'\n",
    "architecture = 'Auto3D2'\n",
    "ny = 3 # number of adjacent B-scans to be considered\n",
    "data_augmentation = True\n",
    "preprocessing = 'normalize'\n",
    "patch_size = 64\n",
    "patch_stride = 4\n",
    "n_bsc = 5 # number of B_scans for training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def parse_pp(string):\n",
    "    return getattr(PreProcessing, string)\n",
    "\n",
    "def parse_net(string):\n",
    "    return getattr(net, string)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "if not os.path.exists(out_path):\n",
    "    os.makedirs(out_path)\n",
    "\n",
    "field, campaign = in_path.split('/')\n",
    "campaign, extension = campaign.split('.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Load Datasets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset = np.load('./datasets/'+str(in_path)).item()\n",
    "\n",
    "train_bsc_idx = np.where(np.asarray(dataset['ground_truth']) == 0)[0][:n_bsc]\n",
    "trainset = dataset['data'][train_bsc_idx]\n",
    "trainset = np.moveaxis(trainset, np.argmin(trainset.shape), -1)\n",
    "del dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### block extraction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "if patch_size is not None:\n",
    "    patch_size = (patch_size, patch_size)\n",
    "else:\n",
    "    patch_size = trainset.shape[1:]\n",
    "\n",
    "patch_size = patch_size + (ny,)\n",
    "patch_stride = (patch_stride, patch_stride, 1)\n",
    "\n",
    "pe = PatchExtractor.PatchExtractor(patch_size, stride=patch_stride)\n",
    "\n",
    "train_patches = pe.extract(trainset)\n",
    "# reshaping\n",
    "train_patches = train_patches.reshape((-1,) + patch_size)\n",
    "\n",
    "# preprocessing each patch\n",
    "train_patches, min_tr, max_tr = PreProcessing.apply_transform(train_patches, transform=parse_pp(preprocessing))\n",
    "\n",
    "# Data augmentation (default=True)\n",
    "if data_augmentation:\n",
    "    train_patches = np.concatenate([train_patches, np.flip(train_patches, axis=2).copy()], axis=0)\n",
    "\n",
    "train_patches = shuffle(train_patches)\n",
    "\n",
    "# create training and validation sets\n",
    "train_patches, val_patches, train_index, val_index = train_test_split(train_patches,\n",
    "                                                                      np.arange(train_patches.shape[0]),\n",
    "                                                                      test_size=0.5,\n",
    "                                                                      random_state=118\n",
    "                                                                      )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CNN Architecture"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/miniconda3/lib/python3.6/site-packages/keras/callbacks.py:999: UserWarning: `epsilon` argument is deprecated and will be removed, use `min_delta` instead.\n",
      "  warnings.warn('`epsilon` argument is deprecated and '\n"
     ]
    }
   ],
   "source": [
    "sets = net.Settings()\n",
    "patience = sets.patience\n",
    "lr_factor = sets.lr_factor\n",
    "batch_size = sets.batch_size\n",
    "epochs = sets.epochs\n",
    "\n",
    "autoencoder, encoder = parse_net(architecture)(patch_size)\n",
    "\n",
    "out_name = field+'_'+campaign+'_'+architecture+'_patch'+str(patch_size[0])+'_stride'+str(patch_stride[0])+'_bsc'+str(n_bsc)+'_ny'+str(ny)\n",
    "\n",
    "lr_chkpt = ReduceLROnPlateau(monitor='val_loss',\n",
    "                             factor=lr_factor,\n",
    "                             patience=patience//2,\n",
    "                             verbose=0,\n",
    "                             mode='auto',\n",
    "                             epsilon=0.0001,\n",
    "                             cooldown=0,\n",
    "                             min_lr=0)\n",
    "save_chkpt = ModelCheckpoint(os.path.join(out_path, out_name+'.h5'),\n",
    "                             monitor='val_loss',\n",
    "                             verbose=1,\n",
    "                             save_best_only=True,\n",
    "                             save_weights_only=True,\n",
    "                             mode='min')\n",
    "stop_chkpt = EarlyStopping(monitor='val_loss',\n",
    "                           patience=patience)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "g_in_0 (InputLayer)          (None, 64, 64, 3)         0         \n",
      "_________________________________________________________________\n",
      "g_conv_0 (Conv2D)            (None, 64, 64, 16)        1744      \n",
      "_________________________________________________________________\n",
      "g_conv_1 (Conv2D)            (None, 32, 32, 16)        6416      \n",
      "_________________________________________________________________\n",
      "g_conv_2 (Conv2D)            (None, 16, 16, 16)        4112      \n",
      "_________________________________________________________________\n",
      "g_conv_3 (Conv2D)            (None, 8, 8, 16)          2320      \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 4, 4, 16)          1040      \n",
      "_________________________________________________________________\n",
      "encoder (Conv2D)             (None, 2, 2, 16)          272       \n",
      "_________________________________________________________________\n",
      "conv2d_transpose_1 (Conv2DTr (None, 4, 4, 16)          1040      \n",
      "_________________________________________________________________\n",
      "g_deconv_0 (Conv2DTranspose) (None, 8, 8, 16)          1040      \n",
      "_________________________________________________________________\n",
      "g_deconv_1 (Conv2DTranspose) (None, 16, 16, 16)        2320      \n",
      "_________________________________________________________________\n",
      "g_deconv_2 (Conv2DTranspose) (None, 32, 32, 16)        4112      \n",
      "_________________________________________________________________\n",
      "g_deconv_3 (Conv2DTranspose) (None, 64, 64, 16)        6416      \n",
      "_________________________________________________________________\n",
      "g_deconv_4 (Conv2DTranspose) (None, 64, 64, 3)         1731      \n",
      "=================================================================\n",
      "Total params: 32,563\n",
      "Trainable params: 32,563\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "autoencoder.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Epoch 00001: val_loss improved from inf to 0.02289, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00002: val_loss improved from 0.02289 to 0.01759, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00003: val_loss improved from 0.01759 to 0.01580, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00004: val_loss improved from 0.01580 to 0.01499, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00005: val_loss improved from 0.01499 to 0.01433, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00006: val_loss improved from 0.01433 to 0.01394, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00007: val_loss improved from 0.01394 to 0.01349, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00008: val_loss improved from 0.01349 to 0.01310, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00009: val_loss improved from 0.01310 to 0.01286, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00010: val_loss improved from 0.01286 to 0.01269, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00011: val_loss did not improve from 0.01269\n",
      "\n",
      "Epoch 00012: val_loss improved from 0.01269 to 0.01247, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00013: val_loss did not improve from 0.01247\n",
      "\n",
      "Epoch 00014: val_loss improved from 0.01247 to 0.01239, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00015: val_loss improved from 0.01239 to 0.01238, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00016: val_loss improved from 0.01238 to 0.01231, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00017: val_loss did not improve from 0.01231\n",
      "\n",
      "Epoch 00018: val_loss improved from 0.01231 to 0.01226, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00019: val_loss did not improve from 0.01226\n",
      "\n",
      "Epoch 00020: val_loss improved from 0.01226 to 0.01224, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00021: val_loss improved from 0.01224 to 0.01221, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00022: val_loss improved from 0.01221 to 0.01213, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00023: val_loss improved from 0.01213 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00024: val_loss improved from 0.01212 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00025: val_loss improved from 0.01212 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00026: val_loss improved from 0.01212 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00027: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00028: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00029: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00030: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00031: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00032: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00033: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00034: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00035: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00036: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00037: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00038: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00039: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00040: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00041: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00042: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00043: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00044: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00045: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00046: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00047: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n",
      "\n",
      "Epoch 00048: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00049: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00050: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00051: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00052: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00053: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00054: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00055: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00056: val_loss did not improve from 0.01211\n",
      "\n",
      "Epoch 00057: val_loss did not improve from 0.01211\n",
      "Training done!\n"
     ]
    }
   ],
   "source": [
    "train = autoencoder.fit(train_patches, train_patches,\n",
    "                        validation_data=(val_patches, val_patches),\n",
    "                        batch_size=batch_size,\n",
    "                        epochs=epochs,\n",
    "                        verbose=0,\n",
    "                        callbacks=[save_chkpt, stop_chkpt, lr_chkpt])\n",
    "print('Training done!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deployment (test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_curve, roc_auc_score\n",
    "from tqdm import tqdm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "training = './cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3'\n",
    "net_weights = training + '.h5'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# in this case, the dataset for training in the same for testing\n",
    "train_path = in_path\n",
    "dataset = np.load('./datasets/' + str(in_path)).item()\n",
    "\n",
    "# preprocessing\n",
    "data = dataset['data']\n",
    "\n",
    "# patch extractor\n",
    "pe = PatchExtractor.PatchExtractor(patch_size, stride=patch_stride)\n",
    "\n",
    "# background bscans for training\n",
    "gt = np.asarray(dataset['ground_truth'])\n",
    "del dataset\n",
    "\n",
    "test_idx = np.arange(data.shape[0])\n",
    "# check whether the test dataset is the same of the training\n",
    "if in_path == train_path:\n",
    "    train_idx = np.where(gt == 0)[0][:n_bsc]\n",
    "    test_idx = np.delete(test_idx, train_idx)\n",
    "testset = data[test_idx]\n",
    "gt = gt[test_idx]\n",
    "del data\n",
    "testset = np.moveaxis(testset, np.argmin(testset.shape), -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load architecture"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "autoencoder, encoder = parse_net(architecture)(patch_size)\n",
    "autoencoder.load_weights(os.path.join(net_weights))\n",
    "\n",
    "out_name = field+'_'+campaign+'_'+architecture+'_patch'+str(patch_size[0])+'_stride'+str(patch_stride[0])+'_bsc'+str(n_bsc)+'_ny'+str(ny)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Test loop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "patches = pe.extract(testset)\n",
    "del testset\n",
    "patchesIdx = patches.shape\n",
    "patches_hat = autoencoder.predict(patches.reshape((-1,) + patch_size))\n",
    "mseFeat = (encoder.predict(patches.reshape((-1,) + patch_size)) - encoder.predict(patches_hat))**2\n",
    "mseFeat_patches = np.zeros(patches_hat.shape) + np.mean(mseFeat, axis=(1,2,3)).reshape((-1,1,1,1))\n",
    "del patches\n",
    "del patches_hat\n",
    "del mseFeat\n",
    "mseFeat_vol = pe.reconstruct(mseFeat_patches.reshape(patchesIdx))\n",
    "del mseFeat_patches"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best AUC = 0.93\n"
     ]
    }
   ],
   "source": [
    "mse_mask_max = np.max(mseFeat_vol, axis=(0, 1))\n",
    "fpr_max, tpr_max, thresholds_max = roc_curve(gt, mse_mask_max)\n",
    "roc_auc_max = roc_auc_score(gt, mse_mask_max)\n",
    "print('best AUC = %0.2f' % roc_auc_max)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
